﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using Slam;
using Slam.Data;
using Slam.Configuration;
using Slam.Events;
using Slam.Logging;
using Slam.SharePoint;
using Slam.Activation;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using SlamCms.SharePoint.SlamProfiles.ContentItem.Data;
using StructureMap;
using System.Xml.Linq;
using SlamCms.SharePoint.SlamProfiles.ContentItem.Services;

namespace SlamCms.SharePoint.SlamProfiles.ContentItem
{
	public class ContentItemDataSynchronizeTypeProcessor : IDataSynchronizeTypeProcessor
	{
		#region Private State

		private IConfigurationManager _configuration;
		private ILogger _logger;

		#endregion

		#region Constructors

		public ContentItemDataSynchronizeTypeProcessor(IConfigurationManager configuration, ILogger logger)
		{
			_configuration = configuration;
			_logger = logger;

			_configuration.RegisterSection("ContentItem",
				config =>
				{
					object section = null;
					if (config is XDocument)
					{
						section = new ContentItemProfileSection((XDocument)config);
					}
					return section;
				});
		}

		#endregion

		#region IDataSynchronizeTypeProcessor Members

		public void Process(string sharepointSiteLocation, IDictionary<string, object> parameters)
		{
			if (parameters["ConfigType"] is ConfiguredType)
			{
				ConfiguredType inputType = (ConfiguredType)parameters["ConfigType"];
				string itemTypeName = inputType.Name;

				if (inputType is SqlConfiguredType)
				{
					SqlConfiguredType sqlType = (SqlConfiguredType)inputType;
					if (!String.IsNullOrEmpty(sqlType.Table))
					{
						itemTypeName = sqlType.Table;
					}
				}

				SPSite site = new SPSite(sharepointSiteLocation);
				string siteCollection = site.ServerRelativeUrl.TrimStart('/');
				DataMapping currentDataMapping = _configuration.DataMapping;
				if (!String.IsNullOrEmpty(siteCollection) && _configuration.DataMappings.ContainsKey(siteCollection))
				{
					currentDataMapping = _configuration.DataMappings[siteCollection];
				}

				ContentItemProfileSection contentItemSection = _configuration.GetSection<ContentItemProfileSection>("ContentItem");
				ContentItemConfiguredType contentItemType = contentItemSection.GetContentItemConfiguredType(siteCollection, inputType.Id, itemTypeName);

				string connectionString = "";
				if (currentDataMapping is XmlSqlDataMapping)
				{
					connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
				}

				if (String.IsNullOrEmpty(connectionString))
				{
					connectionString = _configuration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
				}
				else
				{
					connectionString = _configuration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
				}

				DataService dataService = new DataService(connectionString);
				string dataSchema = ((XmlSqlDataMapping)currentDataMapping).GetDataSchema();
				if (!dataService.VerifySchemaExists(dataSchema))
				{
					dataService.CreateSchema(dataSchema);
				}

				if (!dataService.VerifyContentItemTableExists(dataSchema))
				{
					if (!dataService.VerifyTableExists(dataSchema, "User"))
					{
						dataService.CreateUserTable(dataSchema);
					}

					if (!dataService.VerifyTableExists(dataSchema, "Status"))
					{
						dataService.CreateStatusTable(dataSchema);
					}

					_logger.LogMessage("Information", "ContentItem Load - Create ContentItem");
					dataService.CreateContentItemTable(dataSchema, contentItemSection.GetContentItemFields(currentDataMapping as XmlSqlDataMapping));
				}

				if (!dataService.VerifyContentItemTagTableExists(dataSchema))
				{
					SqlMappedAssociationField tagAssociationField = contentItemSection.GetContentItemTagAssociationField(currentDataMapping as XmlSqlDataMapping);
					dataService.CreateAssociationTable(dataSchema, tagAssociationField.AssociationName, tagAssociationField.FirstAssociatedTypeTableName, tagAssociationField.FirstAssociatedTypeIdColumn, tagAssociationField.FirstAssociatedTypeIdSqlType, tagAssociationField.FirstAssociatedTypeIdSourceColumn, tagAssociationField.SecondAssociatedTypeTableName, tagAssociationField.SecondAssociatedTypeIdColumn, tagAssociationField.SecondAssociatedTypeIdSqlType, tagAssociationField.SecondAssociatedTypeIdSourceColumn);
				}

				if (!dataService.VerifyContentItemFeatureOnTagTableExists(dataSchema))
				{
					SqlMappedAssociationField tagAssociationField = contentItemSection.GetContentItemFeatureOnTagAssociationField(currentDataMapping as XmlSqlDataMapping);
					dataService.CreateContentItemFeatureOnTagTable(dataSchema, tagAssociationField.AssociationName, tagAssociationField.FirstAssociatedTypeTableName, tagAssociationField.FirstAssociatedTypeIdColumn, tagAssociationField.FirstAssociatedTypeIdSqlType, tagAssociationField.FirstAssociatedTypeIdSourceColumn, tagAssociationField.SecondAssociatedTypeTableName, tagAssociationField.SecondAssociatedTypeIdColumn, tagAssociationField.SecondAssociatedTypeIdSqlType, tagAssociationField.SecondAssociatedTypeIdSourceColumn);
				}

				if (!dataService.VerifyContentItemFeatureLocationTableExists(dataSchema))
				{
					SqlMappedAssociationField featureLocationAssociationField = contentItemSection.GetContentItemFeatureLocationAssociationField(currentDataMapping as XmlSqlDataMapping);
					dataService.CreateContentItemFeatureLocationTable(dataSchema, featureLocationAssociationField.AssociationName, featureLocationAssociationField.FirstAssociatedTypeTableName, featureLocationAssociationField.FirstAssociatedTypeIdColumn, featureLocationAssociationField.FirstAssociatedTypeIdSqlType, featureLocationAssociationField.FirstAssociatedTypeIdSourceColumn, featureLocationAssociationField.SecondAssociatedTypeTableName, featureLocationAssociationField.SecondAssociatedTypeIdColumn, featureLocationAssociationField.SecondAssociatedTypeIdSqlType, featureLocationAssociationField.SecondAssociatedTypeIdSourceColumn);
				}

				_logger.LogMessage("Information", "ContentItem Load - Process Type: " + itemTypeName);

				if (!contentItemType.ContentItemOnly)
				{
					parameters["CreateTable"] = true;
				}
				else
				{
					_logger.LogMessage("Information", "ContentItem Load - ContentItem Only - " + itemTypeName);
					parameters["CreateTable"] = false;
				}

				try
				{


					using (SPSite outerSiteCollection = new SPSite(sharepointSiteLocation))
					{
						SPWeb currentWeb = null;

						try
						{
							currentWeb = outerSiteCollection.OpenWeb();
						}
						catch
						{
							//failed
						}

						if (currentWeb != null)
						{

							using (SPSite elevatedSite = new SPSite(currentWeb.Site.ID))
							{
								SPWeb elevatedWeb = null;
								try
								{
									//If Site is empty, open the Root Web
									if (String.IsNullOrEmpty(inputType.Site))
									{
										elevatedWeb = elevatedSite.RootWeb;
									}
									else
									{
										elevatedWeb = elevatedSite.AllWebs[inputType.Site];
									}
								}
								catch
								{
									//failed
								}

								if (elevatedWeb != null && elevatedWeb.Exists)
								{
									if (!inputType.ContentType)
									{
										try
										{
											SPList list = null;


											//If ID is present, try to open the List by ID otherwise just
											//let it fail...
											if (!String.IsNullOrEmpty(inputType.Id))
											{
												list = elevatedWeb.Lists[new Guid(inputType.Id)];
											}
											else
											{
												list = elevatedWeb.Lists[inputType.Name];
											}

											if (list != null)
											{
												SPListItem listItem = list.Items.Add();
												list.AddSlamPropertiesField();
												ContentItemServices.EnsureFrontEndUrlListField(listItem);
												ContentItemServices.EnsureStatusListField(listItem);
												ContentItemServices.EnsureLiveVersionIdField(listItem);
											}
										}
										catch
										{
											//Fails to here if list is not found...
										}

										if (!String.IsNullOrEmpty(currentDataMapping.GetTypeAttribute(inputType.Id, inputType.Name, "AllSites")) && currentDataMapping.GetTypeAttribute(inputType.Id, inputType.Name, "AllSites").ToLower() == "true")
										{
											try
											{
												for (int i = 0; i < elevatedSite.AllWebs.Count; i++)
												{
													SPWeb subWeb = elevatedSite.AllWebs[i];

													SPList list = null;

													//If ID is present, try to open the List by ID otherwise just
													//let it fail...
													if (!String.IsNullOrEmpty(inputType.Id))
													{
														list = subWeb.Lists[new Guid(inputType.Id)];
													}
													else
													{
														list = subWeb.Lists[inputType.Name];
													}

													if (list != null)
													{
														SPListItem listItem = list.Items.Add();
														list.AddSlamPropertiesField();
														ContentItemServices.EnsureFrontEndUrlListField(listItem);
														ContentItemServices.EnsureStatusListField(listItem);
														ContentItemServices.EnsureLiveVersionIdField(listItem);
													}

													subWeb.Dispose();
												}
											}
											catch (Exception exception)
											{
												_logger.LogMessage("Exception", exception.ToString());
											}
										}
									}
									else //If type is a ContentType
									{

										SPContentType contentType = null;
										try
										{
											contentType = elevatedWeb.ContentTypes[inputType.Name];
										}
										catch (Exception exception)
										{
											_logger.LogMessage("Exception", exception.ToString());
										}

										if (contentType != null)
										{
											try
											{
												for (int i = 0; i < elevatedSite.AllWebs.Count; i++)
												{
													SPWeb subWeb = elevatedSite.AllWebs[i];
													for (int k = 0; k < subWeb.Lists.Count; k++)
													{
														SPList list = subWeb.Lists[k];
														try
														{
															if (list.ContentTypes.Contains(contentType))
															{
																SPListItem listItem = list.Items.Add();
																listItem["ContentTypeId"] = contentType.Id;
																list.AddSlamPropertiesField();
																ContentItemServices.EnsureFrontEndUrlContentTypeField(listItem, contentType);
																ContentItemServices.EnsureStatusContentTypeField(listItem, contentType);
																ContentItemServices.EnsureLiveVersionIdField(listItem);
															}
														}
														catch (Exception exception)
														{
															_logger.LogMessage("Exception", exception.ToString());
														}
													}
													subWeb.Dispose();
												}
											}
											catch (Exception exception)
											{
												_logger.LogMessage("Exception", exception.ToString());
											}
										}
									}
									elevatedWeb.Dispose();
								}
							}

							currentWeb.Dispose();
						}
					}

					site.Dispose();
				}
				catch (Exception exception)
				{
					_logger.LogMessage("Exception", exception.ToString());
				}
				

				_logger.LogMessage("Information", "Processing " + inputType.Name);

				string tableName = GetTableName(itemTypeName);
				bool clearItemsOnSynchFinish = true;
				bool cleanupSynchObjects = parameters.ContainsKey("PopulateDataCleanup") && (bool)parameters["PopulateDataCleanup"];

				//If the location tables don't exist, create them
				if (!dataService.VerifyTableExists("SLAM", "List") && parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"])
				{
					dataService.CreateLocationTables();
				}

				IEnumerable<ContentItemField> contentItemFields = contentItemSection.GetTypeContentItemFields(siteCollection, inputType.Id, inputType.Name);
				List<Field> typeFields = currentDataMapping.GetTypeFields(inputType.Id, inputType.Name).ToList();

				for (int i = 0; i < typeFields.Count; i++)
				{
					if (contentItemFields.FirstOrDefault(f => f.Name == typeFields[i].Name) != null)
					{
						typeFields.RemoveAt(i);
						i--;
					}

					if (typeFields[i].Name == "ListID")
					{
						typeFields.RemoveAt(i);
						i--;
					}
				}

				Field idField = currentDataMapping.GetTypeFields(inputType.Id, inputType.Name).ToList()[0];
				string idColumnName = idField.Name;
				string idColumnSqlType = "";
				if (idField is SqlMappedField)
				{
					idColumnName = String.IsNullOrEmpty(((SqlMappedField)idField).Column) ? idColumnName : ((SqlMappedField)idField).Column;
					idColumnSqlType = String.IsNullOrEmpty(((SqlMappedField)idField).SqlType) ? idColumnName : ((SqlMappedField)idField).SqlType;
				}

				//If the Item table doesn't exist for this type and the CreateTable parameter is true, create it.
				//Otherwise if it exists and data will be populated, clear it.
				if (!dataService.VerifyTableExists(dataSchema, tableName) && parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"])
				{
					_logger.LogMessage("Information", "Create table for " + tableName);
					dataService.CreateContentTable(dataSchema, tableName, typeFields as IEnumerable<Field>);
				}
				else if (parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"] && parameters.ContainsKey("PopulateData") && (bool)parameters["PopulateData"])
				{
					_logger.LogMessage("Information", "Prep table for synchronization: " + tableName);
					dataService.AddTableSynchronizationObjects(dataSchema, tableName, idColumnName);
				}

				dataService.UpdateContentTable(dataSchema, tableName, typeFields as IEnumerable<Field>);

				string attachmentTable = currentDataMapping.GetTypeAttribute(inputType.Id, inputType.Name, "AttachmentTableName");

				//If the Attachment table doesn't exist for this type and the CreateTable parameter is true, create it.
				//Otherwise if it exists and data will be populated, clear it.
				if (!String.IsNullOrEmpty(attachmentTable) && !dataService.VerifyTableExists(dataSchema, attachmentTable) && parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"])
				{
					_logger.LogMessage("Information", "Create table for " + attachmentTable);
					dataService.CreateAttachmentTable(dataSchema, attachmentTable, tableName, idColumnName, idColumnSqlType);
				}
				else if (!String.IsNullOrEmpty(attachmentTable) && parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"] && parameters.ContainsKey("PopulateData") && (bool)parameters["PopulateData"])
				{
					_logger.LogMessage("Information", "Prep table for synchronization: " + attachmentTable);
					dataService.AddAttachmentTableSynchronizationObjects(dataSchema, attachmentTable, idColumnName);
				}

				//Check each Association for the type being processed
				foreach (AssociationField association in currentDataMapping.GetTypeAssociations(inputType.Id, inputType.Name))
				{
					if (association is SqlMappedAssociationField && !String.IsNullOrEmpty(association.AssociationName) && !String.IsNullOrEmpty(association.AssociatedTypeName))
					{
						SqlMappedAssociationField sqlField = (SqlMappedAssociationField)association;
						//If this Association table doesn't exist and the CreateTable parameter is true, create it.  
						//Otherwise if it exists and data will be populated, clear it.
						if (!dataService.VerifyTableExists(dataSchema, GetTableName(association.AssociationName)) && parameters.ContainsKey("CreateTable") && (bool)parameters["CreateTable"])
						{
							_logger.LogMessage("Information", "Create table for " + association.AssociationName);
							dataService.CreateAssociationTable(dataSchema, GetTableName(association.AssociationName), sqlField.FirstAssociatedTypeTableName, sqlField.FirstAssociatedTypeIdColumn, sqlField.FirstAssociatedTypeIdSqlType, sqlField.FirstAssociatedTypeIdSourceColumn, sqlField.SecondAssociatedTypeTableName, sqlField.SecondAssociatedTypeIdColumn, sqlField.SecondAssociatedTypeIdSqlType, sqlField.SecondAssociatedTypeIdSourceColumn);
						}
						else if (parameters.ContainsKey("PopulateData") && (bool)parameters["PopulateData"])
						{
							_logger.LogMessage("Information", "Prep association table for synchronization: " + association.AssociationName);
							dataService.AddAssociationTableSynchronizationObjects(dataSchema, GetTableName(association.AssociationName), sqlField.FirstAssociatedTypeIdColumn, sqlField.SecondAssociatedTypeIdColumn);
						}
					}
				}

				try
				{
					//Populate tables with data from SharePoint if the PopulateData parameter is true
					if (parameters.ContainsKey("PopulateData") && (bool)parameters["PopulateData"])
					{
						_logger.LogMessage("Information", "Populating data for " + tableName);
						using (SPSite outerSiteCollection = new SPSite(sharepointSiteLocation))
						{
							SPWeb currentWeb = null;

							try
							{
								currentWeb = outerSiteCollection.OpenWeb();
							}
							catch
							{
								//failed
							}

							if (currentWeb != null)
							{
								SPSecurity.RunWithElevatedPrivileges(delegate()
								{
									using (SPSite elevatedSite = new SPSite(currentWeb.Site.ID))
									{
										SPWeb elevatedWeb = null;
										try
										{
											//If Site is empty, open the Root Web
											if (String.IsNullOrEmpty(inputType.Site))
											{
												elevatedWeb = elevatedSite.RootWeb;
											}
											else
											{
												elevatedWeb = elevatedSite.AllWebs[inputType.Site];
											}
										}
										catch
										{
											//failed
										}

										if (elevatedWeb != null && elevatedWeb.Exists)
										{
											_logger.LogMessage("Information", "Populating data for " + tableName + " from site");

											if (!inputType.ContentType)
											{
												try
												{
													SPList list = null;


													//If ID is present, try to open the List by ID otherwise just
													//let it fail...
													if (!String.IsNullOrEmpty(inputType.Id))
													{
														list = elevatedWeb.Lists[new Guid(inputType.Id)];
													}
													else
													{
														list = elevatedWeb.Lists[inputType.Name];
													}

													if (list != null)
													{
														_logger.LogMessage("Information", "Updating data from List: " + list.RootFolder.ServerRelativeUrl + " for " + tableName);

														//Call update for every item in the list
														for (int i = 0; i < list.Items.Count; i++)
														{
															SPListItem listItem = list.Items[i];
															SlamItemContext.ProcessedByContentType = false;
															SynchronizeListItem(listItem);
														}

														for (int i = 0; i < list.Folders.Count; i++)
														{
															SPListItem listItem = list.Folders[i];
															SlamItemContext.ProcessedByContentType = false;
															SynchronizeListItem(listItem);
														}

														SlamItemContext.ResetContext();
													}
												}
												catch
												{
													//Fails to here if list is not found...
												}

												//Check is the special AllSites option is set to true.  
												//If a type includes an "AllSites" attribute value that is set to
												//true event handlers are attached to ALL Lists with the type Name.
												//Therefore to process a type with AllSites specified as true
												//all of the other lists after the first above need processed.
												if (!String.IsNullOrEmpty(currentDataMapping.GetTypeAttribute(inputType.Id, inputType.Name, "AllSites")) && currentDataMapping.GetTypeAttribute(inputType.Id, inputType.Name, "AllSites").ToLower() == "true")
												{
													try
													{
														for (int i = 0; i < elevatedSite.AllWebs.Count; i++)
														{
															SPWeb subWeb = elevatedSite.AllWebs[i];

															SPList list = null;

															//If ID is present, try to open the List by ID otherwise just
															//let it fail...
															if (!String.IsNullOrEmpty(inputType.Id))
															{
																list = subWeb.Lists[new Guid(inputType.Id)];
															}
															else
															{
																list = subWeb.Lists[inputType.Name];
															}

															if (list != null)
															{
																_logger.LogMessage("Information", "Updating data from List: " + list.RootFolder.ServerRelativeUrl + " for " + tableName);


																//Call update for every item in the list
																for (int k = 0; i < list.Items.Count; k++)
																{
																	SPListItem listItem = list.Items[k];
																	SlamItemContext.ProcessedByContentType = false;
																	SynchronizeListItem(listItem);
																}

																for (int k = 0; i < list.Folders.Count; k++)
																{
																	SPListItem listItem = list.Folders[k];
																	SlamItemContext.ProcessedByContentType = false;
																	SynchronizeListItem(listItem);
																}
																SlamItemContext.ResetContext();
															}

															subWeb.Dispose();
														}
													}
													catch (Exception exception)
													{
														_logger.LogMessage("Exception", exception.ToString());
													}
												}
											}
											else //If type is a ContentType
											{

												SPContentType contentType = null;
												try
												{
													contentType = elevatedWeb.ContentTypes[inputType.Name];
												}
												catch (Exception exception)
												{
													_logger.LogMessage("Exception", exception.ToString());
												}

												if (contentType != null)
												{
													try
													{
														//To process a Content Type, iterate through
														//all sites in the site collection, find each
														//List that contains this Content Type and call
														//IItemUpdater.Update on every item that is of this
														//Content Type.
														for (int i = 0; i < elevatedSite.AllWebs.Count; i++)
														{
															try
															{
																SPWeb subWeb = elevatedSite.AllWebs[i];

																for (int k = 0; k < subWeb.Lists.Count; k++)
																{
																	SPList list = subWeb.Lists[k];
																	if (list.ContentTypes.Contains(contentType))
																	{
																		_logger.LogMessage("Information", "Updating data of Content Type: " + contentType.Name + " from List: " + list.RootFolder.ServerRelativeUrl + " for " + tableName);

																		for (int j = 0; i < list.Items.Count; j++)
																		{
																			SPListItem listItem = list.Items[j];
																			//Check if this SPListItem is of the Content Type being processed,
																			//if it matches call Update.
																			if (listItem.ContentType != null && listItem.ContentType.EqualsOrInherits(inputType.Name))
																			{
																				SlamItemContext.ProcessedByContentType = true;
																				SynchronizeListItem(listItem);
																			}
																		}
																		SlamItemContext.ResetContext();

																		//In case this Content Type applies to folders,
																		//check the List's Folders as well.  These items would
																		//not be processed above
																		for (int j = 0; i < list.Folders.Count; j++)
																		{
																			SPListItem listItem = list.Folders[j];
																			if (listItem.ContentType != null && listItem.ContentType.EqualsOrInherits(inputType.Name))
																			{
																				SlamItemContext.ProcessedByContentType = true;
																				SynchronizeListItem(listItem);
																			}
																		}
																		SlamItemContext.ResetContext();
																	}
																}
																subWeb.Dispose();
															}
															catch (Exception exception)
															{
																_logger.LogMessage("Exception", exception.ToString());
															}
														}
													}
													catch (Exception exception)
													{
														_logger.LogMessage("Exception", exception.ToString());
													}
												}
											}
											elevatedWeb.Dispose();
										}
									}
								});

								currentWeb.Dispose();
							}
						}
					}

					site.Dispose();
				}
				catch (Exception exception)
				{
					_logger.LogMessage("Exception", exception.ToString());
					clearItemsOnSynchFinish = false;
				}
				finally
				{
					if (cleanupSynchObjects)
					{	
						int deleted = dataService.DropContentItemTableSynchronizationObjects(dataSchema, tableName, clearItemsOnSynchFinish, "ContentItemId", site.ID.ToString());
						_logger.LogMessage("Information", "Number of items removed from " + tableName + ": " + deleted.ToString());

						if (!String.IsNullOrEmpty(attachmentTable))
						{
							deleted = dataService.DropContentItemTableSynchronizationObjects(dataSchema, attachmentTable, clearItemsOnSynchFinish, "ContentItemId", site.ID.ToString());
							_logger.LogMessage("Information", "Number of items removed from " + attachmentTable + ": " + deleted.ToString());
						}

						foreach (AssociationField association in currentDataMapping.GetTypeAssociations(inputType.Id, inputType.Name))
						{
							deleted = dataService.DropContentItemTableSynchronizationObjects(dataSchema, GetTableName(association.AssociationName), clearItemsOnSynchFinish, ((SqlMappedAssociationField)association).FirstAssociatedTypeIdColumn, site.ID.ToString());
							_logger.LogMessage("Information", "Number of items removed from " + GetTableName(association.AssociationName) + ": " + deleted.ToString());
						}
					}
				}
			}
		}
		#endregion

		private string GetTableName(string typeName)
		{
			return Regex.Replace(typeName, "[\\W\\s]", "");
		}

		private void SynchronizeListItem(SPListItem listItem)
		{
			string id = Convert.ToString(listItem.GetUniqueId(false, false));
			if (String.IsNullOrEmpty(id))
			{
				listItem["SLAM ID"] = listItem.GetUniqueId(true, false);
				listItem.SystemUpdate(false);
			}
			listItem.SlamUpdate();
		}
	}
}

